Skip to content

feat(native-passkey): Bearer-authenticated passkey list + delete#105

Merged
windischb merged 1 commit into
developfrom
feat/native-passkey-list-delete
Jun 28, 2026
Merged

feat(native-passkey): Bearer-authenticated passkey list + delete#105
windischb merged 1 commit into
developfrom
feat/native-passkey-list-delete

Conversation

@windischb

Copy link
Copy Markdown
Contributor

What & why

Adds the cookieless, Bearer-authenticated half of native passkey management so a native client (iOS) or a brokering web BFF can offer a "your passkeys" / "revoke a lost device" surface from the access token it already holds.

Until now modgud could enroll a passkey and log in with it cookielessly (ADR-0009 / ADR-0010), but the only way to list or delete a user's passkeys was the cookie-based realm UI (/api/account/passkey), which needs a modgud session — something neither the native client nor the amZettel BFF has. Driver FR: amZettel.

The two endpoints

Endpoint Behavior
GET /connect/passkey The token subject's own passkeys → [{ Id, DisplayName, CreatedAt, LastUsedAt }]
DELETE /connect/passkey/{id} Revoke one own passkey → 204; unknown id or one owned by another user → 404 (never 403)
  • Both gated exactly like the enroll endpoints: per-realm NativeGrants flag + the OpenIddict validation (Bearer) scheme.
  • Subject is resolved store-backed (SecurityStamp-authoritative), never trusting token claims as the user record.
  • Strictly owner-scoped — a caller only ever sees/deletes credentials owned by the token's subject; the 404-not-403 keeps it from being a cross-user credential-existence oracle.
  • A deleted passkey can no longer satisfy a urn:cocoar:passkey assertion.

Design note

The list is user-scoped (every passkey the user holds, not filtered by the calling client's RP-ID). Rationale: the FR's primary driver is "revoke a lost device" (needs full visibility), and it mirrors the existing cookie-based Passkey_List exactly, so both management surfaces agree. The FR flagged RP-ID filtering as "optional" — easy to add if we want a per-app view later.

Refactor

The NativeGrants gate and the store-backed principal resolution are lifted out of NativePasskeyEnrollEndpoints into a shared NativeBearerEndpointSupport, so the enroll and management paths can't drift on the security contract.

Tests

8 new integration tests in CocoarPasskeyGrantFlowTests.Management.cs:

  • list returns only own credentials (full DTO shape), 401 anon, 400 when NativeGrants off
  • delete own → gone, delete foreign → 404 + untouched, unknown id → 404, 401 anon
  • end-to-end delete-then-native-login-fails proving a revoked passkey is no longer redeemable

Full Cocoar* native-grant suite green (35); full solution (Modgud.slnx) builds with 0 errors.

Docs

docs/integrate/native-apps.md — new "Manage passkeys — list & revoke" section + error-table rows.

🤖 Generated with Claude Code

Add the cookieless, Bearer-authenticated half of native passkey management
so a native client (or a brokering web BFF) can show "your passkeys" and
revoke a lost device from the access token it already holds — closing the
gap left by the enroll/login ceremony, where the only list/delete surface
was the cookie-based realm UI (which needs a Modgud session).

- GET /connect/passkey — the token subject's own passkeys, user-scoped
  (mirrors the cookie-based Passkey_List so both management surfaces agree).
- DELETE /connect/passkey/{id} — revoke one own passkey; an unknown id or
  one owned by another user is a 404 (never 403 — no cross-user oracle).

Both gated by the per-realm NativeGrants flag and the OpenIddict validation
(Bearer) scheme, exactly like the enroll endpoints. The NativeGrants gate
and the store-backed (SecurityStamp-authoritative) principal resolution are
lifted into a shared NativeBearerEndpointSupport so the enroll and management
paths cannot drift.

Tests: 8 new integration tests (list owner-scoping + 401 + flag-off; delete
own/foreign/unknown/anon; and a delete-then-native-login-fails end-to-end
proving a revoked passkey is no longer redeemable). Full Cocoar native-grant
suite green (35). Docs: native-apps.md "Manage passkeys" section + error rows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@windischb windischb merged commit db040cc into develop Jun 28, 2026
8 checks passed
@windischb windischb deleted the feat/native-passkey-list-delete branch June 28, 2026 20:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant